package com.topvision.myviewtest.gscview;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.widget.NestedScrollView;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;

/**
 * @author lucy
 * @date 2018-12-05 13:28
 * @description //TODO
 */

public class ExtendView extends LinearLayout {
    private static final String TAG = "ExtendView";
    private static final int VELOCITY_THRESHOLD = 6;
    /* 滑动时间 */
    private static final int SCROLL_DURATION = 300;
    private static final int STATE_EXTEND = 1;
    private static final int STATE_CLOSE = 2;
    private View mHeadView;
    private int mHeadHeight;
    private int mExtendHeight;
    private float mStartScrollY = -1;
    private SmoothScrollRunnable mSmoothScrollRunnable;
    private VelocityTracker mVelocityTracker;
    private float mYVelocity;
    private RecyclerView mRecyclerView;
    private ListView mListView;
    private ScrollView mScrollView;
    private NestedScrollView mNestedScrollView;
    private boolean isInterceptTouchEvent = false;
    private OnExtendStateChangeListener mExtendStateListener;
    private int mLastState;
    /* 默认开启滑动 */
    private boolean mEnable = true;

    public ExtendView(Context context) {
        this(context, null);
    }

    public ExtendView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ExtendView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        init();
    }

    private void init() {
        int count = getChildCount();
        if (count < 2) {
            throw new IllegalStateException("child view must be no less than 2");
        }
        mHeadView = getChildAt(0);
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                setPadding();
                getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
        for (int i = 1; i < count; i++) {
            View view = getChildAt(i);
            if (view instanceof ScrollView
                    && view.getVisibility() == VISIBLE) {
                mScrollView = (ScrollView) view;
            } else if (view instanceof NestedScrollView
                    && view.getVisibility() == VISIBLE) {
                mNestedScrollView = (NestedScrollView) view;
            } else if (view instanceof RecyclerView
                    && view.getVisibility() == VISIBLE) {
                mRecyclerView = (RecyclerView) view;
            } else if (view instanceof ListView
                    && view.getVisibility() == VISIBLE) {
                mListView = (ListView) view;
            }
        }
        mLastState = STATE_CLOSE;
        mVelocityTracker = VelocityTracker.obtain();
    }

    private void setPadding() {
        mHeadHeight = mHeadView.getMeasuredHeight();
        LinearLayout.LayoutParams layoutParams = (LayoutParams) mHeadView.getLayoutParams();
        mExtendHeight = mHeadHeight + mHeadView.getPaddingTop() + layoutParams.topMargin;
        setPadding(getPaddingLeft(), -mExtendHeight, getPaddingRight(), getPaddingBottom());
        requestLayout();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!mEnable) return false;
        isInterceptTouchEvent = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mStartScrollY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                interceptScroll(mScrollView, event);
                interceptScroll(mNestedScrollView, event);
                interceptRecyclerView(mRecyclerView, event);
                interceptLListView(mListView, event);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                break;
            default:
                break;
        }
        return isInterceptTouchEvent;
    }

    private void interceptLListView(ListView listView, MotionEvent event) {
        if (listView == null) return;
        // 手指向下滑
        if (event.getY() - mStartScrollY > 0) {
            isInterceptTouchEvent = isListViewReachTopEdge(listView);
        } else {
            isInterceptTouchEvent = mScrollCount != 0;
        }
        mStartScrollY = event.getY();
    }

    private boolean isListViewReachTopEdge(ListView listView) {
        if (listView.getFirstVisiblePosition() == 0) {
            View topChild = listView.getChildAt(0);
            return topChild.getTop() == 0;
        }
        return false;
    }

    private void interceptRecyclerView(RecyclerView recyclerView, MotionEvent event) {
        if (recyclerView == null) return;
        // 手指向下滑
        if (event.getY() - mStartScrollY > 0) {
            isInterceptTouchEvent = !recyclerView.canScrollVertically(-1);
        } else {
            isInterceptTouchEvent = mScrollCount != 0;
        }
        mStartScrollY = event.getY();
    }

    private void interceptScroll(View view, MotionEvent event) {
        if (view == null) return;
        if (!(view instanceof ScrollView) && !(view instanceof NestedScrollView)) return;
        // 手指向下滑
        if (event.getY() - mStartScrollY > 0) {
            // 展开状态
            if (-mScrollCount != mExtendHeight) {
                isInterceptTouchEvent = view.getScrollY() == 0;
            } else {
                isInterceptTouchEvent = false;
            }
        } else {
            isInterceptTouchEvent = mScrollCount != 0;
        }
        mStartScrollY = event.getY();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isInterceptTouchEvent) return false;
        mVelocityTracker.addMovement(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mStartScrollY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float deltaY = event.getY() - mStartScrollY;
                mStartScrollY = event.getY();
                scrollTouch(deltaY);
                mVelocityTracker.computeCurrentVelocity(1);
                mYVelocity = mVelocityTracker.getYVelocity();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                smoothScroll();
                break;
            default:
                break;
        }
        return true;
    }

    private void smoothScroll() {
        if (mScrollCount == 0 || mScrollCount == mExtendHeight) return;
        if (null != mSmoothScrollRunnable) {
            mSmoothScrollRunnable.stop();
        }
        // 1/2 colse
        boolean b = Math.abs(mScrollCount * 2) <= mExtendHeight;
        int smoothValue = b ? 0 : -mExtendHeight;
        if (mYVelocity > VELOCITY_THRESHOLD) {
            smoothValue = -mExtendHeight;
        } else if (mYVelocity < -VELOCITY_THRESHOLD) {
            smoothValue = 0;
        }
        mSmoothScrollRunnable = new SmoothScrollRunnable(getScrollY(), smoothValue, SCROLL_DURATION);
        post(mSmoothScrollRunnable);
    }

    private void smoothScroll(int scrollValue) {
        if (null != mSmoothScrollRunnable) {
            mSmoothScrollRunnable.stop();
        }
        mSmoothScrollRunnable = new SmoothScrollRunnable(getScrollY(), scrollValue, SCROLL_DURATION);
        post(mSmoothScrollRunnable);
    }

    private int mScrollCount;

    private void scrollTouch(float deltaY) {
        if (deltaY > 0) {
            // 手指向下滑
            if (mScrollCount >= mExtendHeight) return;
            mScrollCount += deltaY;
            if (mScrollCount >= mExtendHeight) {
                mScrollCount = mExtendHeight;
                scrollTo(0, -mScrollCount);
                onExtend();
            } else {
                scrollBy(0, (int) -deltaY);
            }
        } else {
            // scroll up
            if (mScrollCount <= 0) return;
            mScrollCount += deltaY;
            if (mScrollCount <= 0) {
                mScrollCount = 0;
                scrollTo(0, -mScrollCount);
                onClose();
            } else {
                scrollBy(0, (int) -deltaY);
            }
        }
    }


    class SmoothScrollRunnable implements Runnable {
        private long mDuration;
        private DecelerateInterpolator mInterpolator;
        private int mScrollFromY;
        private boolean mContinueRunning = true;
        private int mScrollToY = -1;
        private int mCurrentY = -1;
        private long mStartTime = -1;

        public SmoothScrollRunnable(int fromY, int toY, long duration) {
            mScrollFromY = fromY;
            mScrollToY = toY;
            mDuration = duration;
            mInterpolator = new DecelerateInterpolator();
        }

        @Override
        public void run() {
            if (mDuration <= 0) {
                scrollTo(0, mScrollToY);
                return;
            }
            if (mStartTime == -1) {
                mStartTime = System.currentTimeMillis();
            } else {
                long oneSecond = 1000;
                long normalizedTime = (oneSecond * (System.currentTimeMillis() - mStartTime)) / mDuration;
                normalizedTime = Math.max(Math.min(normalizedTime, oneSecond), 0);
                int deltaY = Math.round((mScrollFromY - mScrollToY)
                        * mInterpolator.getInterpolation(normalizedTime / (float) oneSecond));
                mCurrentY = mScrollFromY - deltaY;
                mScrollCount = -mCurrentY;
                scrollTo(0, mCurrentY);
            }
            if (mContinueRunning && mScrollToY != mCurrentY) {
                postDelayed(this, 16);
            }
            // 状态回调
            if (mScrollToY == mCurrentY) {
                if (mCurrentY == 0) {
                    onClose();
                } else {
                    onExtend();
                }
            }
        }

        public void stop() {
            mContinueRunning = false;
            removeCallbacks(this);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }

    private void onExtend() {
        if (mExtendStateListener != null && mLastState != STATE_EXTEND) {
            mExtendStateListener.onExtend();
        }
        mLastState = STATE_EXTEND;
    }

    private void onClose() {
        if (mExtendStateListener != null && mLastState != STATE_CLOSE) {
            mExtendStateListener.onClose();
        }
        mLastState = STATE_CLOSE;
    }

    private void setEnable(boolean enable) {
        mEnable = enable;
    }

    public interface OnExtendStateChangeListener {
        /**
         * 展开
         */
        void onExtend();

        /**
         * 关闭
         */
        void onClose();
    }

    /* 外部调用函数 */

    public void setupWithRecyclerView(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
    }

    public void setupWithListView(ListView listView) {
        mListView = listView;
    }

    public void setupWithScrollView(ScrollView scrollView) {
        mScrollView = scrollView;
    }

    public void setupWithNestedScrollView(NestedScrollView nestedScrollView) {
        mNestedScrollView = nestedScrollView;
    }

    public void setOnExtendStateChangeListener(OnExtendStateChangeListener listener) {
        mExtendStateListener = listener;
    }

    public void enableExtend() {
        setEnable(true);
    }

    public void disableExtend() {
        setEnable(false);
    }

    public void close() {
        if (mScrollCount != 0) {
            smoothScroll(0);
        }
    }

    public void extend() {
        if (mScrollCount != mHeadHeight) {
            smoothScroll(-mHeadHeight);
        }
    }
}

